﻿using System;
using System.Text;
using Selenium;
using ThoughtWorks.Selenium.Silvernium;

namespace Quail.Silverlight
{
    /// <summary>
    /// The Silverlight Page manager
    /// </summary>
    public class SlvPage : SlvControlContainer
    {
        private bool _inSilverlightCheck, _silverniumLoaded;
        private int _noSilverlightExceptionCheck;

        /// <summary>
        /// Initializes a new instance of the <see cref="SlvPage"/> class.
        /// </summary>
        /// <param name="slvContainer">The silverlight container.</param>
        /// <param name="slvPageName">The silverlight page name.</param>
        public SlvPage(SlvContainer slvContainer, string slvPageName)
            : base(null)
        {
            SlvContainer = slvContainer;
            PageName = slvPageName;
            SilverniumObject = new Silvernium(SlvContainer.Browser.SeleniumInterface, SlvContainer.SlvObjectName, PageName);
            PageSelector = SilverniumObject.SilverLightJSStringPrefix + "Content." + PageName;
        }

        /// <summary>
        /// Gets the name of the silveright page
        /// </summary>
        public string PageName
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the page selector
        /// </summary>
        public string PageSelector
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the silverlight container
        /// </summary>
        public SlvContainer SlvContainer
        {
            get; private set;
        }

        /// <summary>
        /// Gets the containing web page
        /// </summary>
        public override WebPage Page
        {
            get
            {
                return SlvContainer.Page;
            }
        }

        /// <summary>
        /// Gets the current WebBrowser instance
        /// </summary>
        public override WebBrowser Browser
        {
            get { return SlvContainer.Browser; }
        }

        /// <summary>
        /// Gets the ActiveTask object
        /// </summary>
        public override Task ActiveTask
        {
            get { return SlvContainer.ActiveTask; }
        }

        /// <summary>
        /// Gets a value indicating whether this <see cref="SlvPage"/> is visible.
        /// </summary>
        /// <value><c>true</c> if visible; otherwise, <c>false</c>.</value>
        public override bool Visible
        {
            get
            {
                return Exists;
            }
        }

        /// <summary>
        /// Gets a value indicating whether this <see cref="SlvPage"/> is enabled.
        /// </summary>
        /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
        public override bool Enabled
        {
            get
            {
                return Exists;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the silverlight page exists or not
        /// </summary>
        public override bool Exists
        {
            get
            {
                using (Browser.CacheLocation())
                {
                    if (!Page.ExistsNoLog)
                        return false;

                    var task = ActiveTask.BeginTask("SlvPage.Exists");
                    task.AddAttribute("PageName", PageName);
                    try
                    {
                        //task.AddLog(LogStatus.Information, "!SilverniumObject.IsLoaded()");
                        if (!_silverniumLoaded)
                        {
                            if (!SilverniumObject.IsLoaded())
                            {
                                task.Rollback();
                                return false;
                            }

                            _silverniumLoaded = true;
                        }

                        //task.AddLog(LogStatus.Information, PageSelector + "==undefined");

                        var js = "var p=" + PageSelector + ";p && p.Visible";
                        if (SeleniumInterface.GetEval(js) != "true")
                        {
                            task.Rollback();
                            return false;
                        }

                        /*if (SeleniumInterface.GetEval(PageSelector + "==undefined") == "true")
                        {
                            task.Rollback();
                            return false;
                        }

                        if (SeleniumInterface.GetEval(PageSelector + ".Visible") == "false")
                        {
                            task.Rollback();
                            return false;
                        }*/

                        // SilverlightExceptionCheck();

                        task.Rollback();
                        return true;
                    }
                    catch (Exception e)
                    {
                        task.Finish(e);
                        throw;
                    }
                }
            }
        }

        /// <summary>
        /// Gets or sets the selector.
        /// </summary>
        /// <value>The selector.</value>
        public override string Selector
        {
            get { return PageSelector; }
            set { throw new NotImplementedException(); }
        }

        /// <summary>
    	/// Gets a value indicating whether IsProcessing.
    	/// </summary>
    	public bool IsProcessing
		{
			get
			{
				bool returnValue;

				try
				{
                    var js = "try { " + PageSelector + ".IsProcessing == true; } catch (e) { false; }";
                    var result = SeleniumInterface.GetEval(js);
					returnValue = bool.Parse(result);
				}
				catch (Exception)
				{
					returnValue = false;
				}

				return returnValue;
			}
		}

    	/// <summary>
    	/// Gets a value indicating whether IsSaving.
    	/// </summary>
    	public bool IsSaving
		{
			get
			{
				bool returnValue;

				try
				{
					var result = CallMethod("IsSaving");
					returnValue = bool.Parse(result);
				}
				catch (Exception)
				{
					returnValue = false;
				}

				return returnValue;
			}
		}

        /// <summary>
        /// Performs actions on the page based on the options and optional parameters
        /// </summary>
        /// <param name="options">The type of excercise to perform</param>
        /// <param name="parms">Optional parameters for the page</param>
        public virtual void Exercise(ExerciseOptions options, params object[] parms)
        {
            throw new NotImplementedException("Exercise must be implemented in sub-classes");
        }

        /// <summary>
        /// Begins a new task inside the page
        /// </summary>
        /// <param name="taskname">Name of the new task</param>
        /// <returns>The task object</returns>
        public Task BeginTask(string taskname)
        {
            var result = ActiveTask.BeginTask(taskname);
            result.AddAttribute("SlvPage", PageName);
            return result;
        }

        /// <summary>
        /// Begins a new task inside the page
        /// </summary>
        /// <param name="taskname">Name of the new task</param>
        /// <param name="message">Message for the task</param>
        /// <returns>the task object</returns>
        public Task BeginTask(string taskname, string message)
        {
            var result = ActiveTask.BeginTask(taskname, message);
            result.AddAttribute("SlvPage", PageName);
            return result;
        }

        /// <summary>
        /// Begins a new task inside the page
        /// </summary>
        /// <param name="taskname">Name of the new task</param>
        /// <param name="message">Message for the task</param>
        /// <param name="values">values to be formatted into the message</param>
        /// <returns>the task object</returns>
        public Task BeginTask(string taskname, string message, params object[] values)
        {
            var result = ActiveTask.BeginTask(taskname, message, values);
            result.AddAttribute("SlvPage", PageName);
            return result;
        }

    	/// <summary>
        /// Waits for the silverlight page to finish loading
        /// </summary>
        public void Sync()
        {
            var task = BeginTask("Sync");
            try
            {

                SlvContainer.Maximize();

                new WaitParameters("Sync")
                    {
                        PollingInterval = 200,
                        FailMessage = "Sync Timed out",
                        Delegate = () =>
                                       {
                                           if (Visible && !IsProcessing)
                                           {
                                               SilverlightExceptionCheck();
                                               return true;
                                           }

                                           return false;
                                       },
                        ThrowOnStatusFailTimeout = true
                    }
                    .Wait(task);

                SlvContainer.Browser.Sleep(200);

                task.Finish();
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

		/// <summary>
		/// Executes a method on the silverlight control
		/// </summary>
		/// <param name="method">name of method to execute</param>
		/// <param name="methodparms">parameters for the method</param>
		/// <returns>the return value of the method</returns>
        public string CallMethod(string method, params string[] methodparms)
		{
		    return CallMethod(BeginTask("CallMethod"), method, methodparms);
		}

        /// <summary>
		/// Executes a method on the silverlight control
		/// </summary>
		/// <param name="task">The task to use</param>
		/// <param name="method">name of method to execute</param>
		/// <param name="methodparms">parameters for the method</param>
		/// <returns>the return value of the method</returns>
		public string CallMethod(Task task, string method, params string[] methodparms)
		{
			try
			{
                using (Browser.CacheLocation())
                {

                    task.AddAttribute("MethodName", method);
                    var str = new StringBuilder("(");
                    var first = true;
                    Array.ForEach(methodparms, st =>
                                                   {
                                                       if (first)
                                                           first = false;
                                                       else
                                                           str.Append(",");
                                                       str.Append(st);
                                                   });
                    str.Append(")");
                    task.AddAttribute("Params", str.ToString());
                    SilverlightExceptionCheck();
                    var result = SilverniumObject.Call(method, methodparms);
                    task.Finish();

                    return result;
                }
			}
			catch (Exception e)
			{
				task.Finish(e);
				throw;
			}
		}

        /// <summary>
        /// Gets a property value
        /// </summary>
        /// <param name="task">the task</param>
        /// <param name="propertyName">name of the property</param>
        /// <returns>The property value</returns>
        public string GetPropertyValue(Task task, string propertyName)
        {
            try
            {
                task.AddAttribute("PropName", propertyName);
                SilverlightExceptionCheck();
                var result = SilverniumObject.GetPropertyValue(propertyName);
                task.AddAttribute("Result", result);
                task.Finish();
                return result;
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Gets a property value from an array from the control
        /// </summary>
        /// <param name="task">the task</param>
        /// <param name="propertyName">name of the property</param>
        /// <returns>The property value</returns>
        public string[] GetPropertyValueArray(Task task, string propertyName)
        {
            try
            {
                task.AddAttribute("PropName", propertyName);
                SilverlightExceptionCheck();
                var result = SilverniumObject.GetPropertyValue(propertyName);
                task.AddAttribute("Result", result);
                task.Finish();
                return HttpCommandProcessor.parseCSV(result);
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }


        /// <summary>
        /// Sets a property value to a silverlight control
        /// </summary>
        /// <param name="task">the task</param>
        /// <param name="propertyName">the property to set</param>
        /// <param name="value">the new value</param>
        public void SetPropertyValue(Task task, string propertyName, string value)
        {
            try
            {
                task.AddAttribute("PropName", propertyName);
                task.AddAttribute("NewValue", value);
                SilverlightExceptionCheck();
                SilverniumObject.SetPropertyValue(propertyName, value);
                task.Finish();
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }
		/// <summary>
		/// If the page doesn't Exist, it attempts to Navigate to the page
		/// </summary>
		public void NavigateToThisPageIfNotExists()
		{
			if (Exists)
				Sync();
			else
				NavigateToThisPage();
		}

		/// <summary>
		/// Navigates to this page
		/// </summary>
		public virtual void NavigateToThisPage()
		{
			throw new NotImplementedException("NavigateToThisPage must be implemented in sub-classes");
		}

		/// <summary>
		/// Attempts to fill out the page with meaningful data
		/// </summary>
		public virtual void FillOut()
		{
			throw new NotImplementedException("FillOut must be implemented in sub-classes");
		}

        /// <summary>
        /// Checks for Silverlight exceptions (sub-classes should implement SilverlightExceptionCheckImpl)
        /// </summary>
        public void SilverlightExceptionCheck()
        {
            if (_noSilverlightExceptionCheck != 0 || _inSilverlightCheck)
                return;

            _inSilverlightCheck = true;
            try
            {
                SilverlightExceptionCheckImpl();
            }
            finally
            {
                _inSilverlightCheck = false;
            }
        }

        /// <summary>
        /// Stops the silverlight exception check - you must dispose of the returned object to re-enable silverlight exception checking.
        /// </summary>
        /// <returns>A disposable object</returns>
        public IDisposable StopSilverlightExceptionCheck()
        {
            return new StopSilverlightExceptionClass(this);
        }

        /// <summary>
        /// Calls SeleniumInterface.GetEval();
        /// </summary>
        /// <param name="eval"></param>
        /// <returns></returns>
        public string GetEval(string eval)
        {
            SilverlightExceptionCheck();
            return SeleniumInterface.GetEval(eval);
        }


        /// <summary>
        /// Checks for Silverlight exceptions (must be implemented in sub-classes)
        /// </summary>
        protected virtual void SilverlightExceptionCheckImpl()
        {
        }

        private void EndStopSilverlightException()
        {
            _noSilverlightExceptionCheck--;

            if (_noSilverlightExceptionCheck < 0)
                throw new Exception("Too many calls to EndStopSilverlightException");
        }

        private void BeginStopSilverlightException()
        {
            _noSilverlightExceptionCheck++;
        }

        /// <summary>
        /// Gets the Selenium interface
        /// </summary>
        private ISelenium SeleniumInterface
        {
            get { return SlvContainer.Page.Browser.SeleniumInterface; }
        }

        /// <summary>
        /// Gets the silvernium object
        /// </summary>
        private Silvernium SilverniumObject
        {
            get;
            set;
        }

        /// <summary>
        /// Standard exception class for the SlvPage
        /// </summary>
        public class SlvPageException : LogStatusException
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="SlvPageException"/> class.
            /// </summary>
            /// <param name="page">The silverlight page.</param>
            /// <param name="message">The message.</param>
            public SlvPageException(SlvPage page, string message)
                : this(page, LogStatus.Fail, message)
            {
            }

            /// <summary>
            /// Initializes a new instance of the <see cref="SlvPageException"/> class.
            /// </summary>
            /// <param name="page">The silverlight page.</param>
            /// <param name="status">The status to log</param>
            /// <param name="message">The message.</param>
            public SlvPageException(SlvPage page, LogStatus status, string message)
                : base(status, message)
            {
                SlvPage = page;
            }

            /// <summary>
            /// Gets SlvPage.
            /// </summary>
            public SlvPage SlvPage
            {
                get; private set;
            }
        }

        /// <summary>
        /// Exception thrown when the "OSOD" is shown  (orange screen of death)
        /// </summary>
        public class SlvExceptionDialogShowing : SlvPageException
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="SlvExceptionDialogShowing"/> class.
            /// </summary>
            /// <param name="page">The silverlight page.</param>
            /// <param name="message">The message.</param>
            public SlvExceptionDialogShowing(SlvPage page, string message)
                : base(page, LogStatus.FailYsod, message)
            {
            }
        }

        private class StopSilverlightExceptionClass : IDisposable
        {
            private readonly SlvPage _page;

            public StopSilverlightExceptionClass(SlvPage page)
            {
                _page = page;
                _page.BeginStopSilverlightException();
            }

            public void Dispose()
            {
                _page.EndStopSilverlightException();
            }
        }
    }
}
